package context.render.vertexdata;

import context.util.Data;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;

public class VertexData
{
    
    public static final VertexData QUAD_FULL = new VertexData(new int[] { 2 }, GL11.GL_QUADS, GL15.GL_STATIC_DRAW);
    static
    {
        VertexData.QUAD_FULL.addVert(new double[] { -1, -1 });
        VertexData.QUAD_FULL.addVert(new double[] {  1, -1 });
        VertexData.QUAD_FULL.addVert(new double[] {  1,  1 });
        VertexData.QUAD_FULL.addVert(new double[] { -1,  1 });
        VertexData.QUAD_FULL.compileData();
    }
    
    
    private int mID;
    
    private int mNumVerts;
    
    /** An array containing the number of floats per vertex attribute. e.g. [3,3,2] for pos, norm, texuv. */
    private int[] mFloatsPerAttrib;
    
    /** The number of floats per vertex spanning across all per-vertex attributes. */
    private int mFloatsPerVert;
    
    /** Geometry type, e.g. GL_TRIANGLES, GL_QUADS, GL_POINT. */
    private int mGeometryType;
    
    /** Usage type, e.g. STATIC_DRAW, STREAM_DRAW, etc. */
    private int mUsageType;
    
    private LinkedList<double[]> mListVerts;
    
    private boolean mCompiled;
    
    
    public VertexData(
            int[] aFloatsPerAttrib, 
            final int aGeometryType, 
            final int aUsageType)
    {
        IntBuffer id = Data.get().reserveDataInt(1);
        GL15.glGenBuffers(id);
        this.mID = id.get(0);
        
        this.mNumVerts = 0;
        
        int numAttrib = aFloatsPerAttrib.length;
        this.mFloatsPerAttrib = new int[numAttrib];
        System.arraycopy(aFloatsPerAttrib, 0, this.mFloatsPerAttrib, 0, numAttrib);
        
        this.mFloatsPerVert = this.sumOver(this.mFloatsPerAttrib);
        
        this.mGeometryType = aGeometryType;
        
        this.mUsageType = aUsageType;
        
        this.mListVerts = new LinkedList<>();
        
        this.mCompiled = false;
    }
    
    
    public void addVert(double[] aVert)
    {
        this.mListVerts.add(aVert);
        this.mNumVerts++;
    }
    
    public void compileData()
    {
        // Prepare a buffer of floats for all interleaved data:
        int numFloats = this.mFloatsPerVert * this.mNumVerts;
        FloatBuffer vertData = Data.get().reserveDataFloat(numFloats);
        // Populate that buffer with all data that's been added:
        while (!this.mListVerts.isEmpty())
        {
            double[] vertex = this.mListVerts.removeFirst();
            for (double elem : vertex)
            {
                vertData.put((float) elem);
            }
        }
        // Reset the buffer:
        vertData.flip();
        // Transfer that data block to GPU VRAM at some point in the near future:
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.mID);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertData, this.mUsageType);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        // Check this object as compiled so that it can be sent to the GPU:
        this.mCompiled = true;
    }
    
    public void send()
    {
        // Check for compilation before sending nonexistent data:
        if (!this.mCompiled)
        {
            System.err.println("VertexData has not been compiled! Cannot be sent to GPU!");
            System.exit(1);
        }
        // Bind the VBO and point the GPU to the interleaved data:
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.mID);
        long byteOffset = 0L;
        int numAttribs = this.mFloatsPerAttrib.length;
        for (int indAttrib = 0; indAttrib < numAttribs; indAttrib++)
        {
            // Point the GPU to the data:
            GL20.glVertexAttribPointer(
                indAttrib, // Attribute index
                this.mFloatsPerAttrib[indAttrib], // Number of floats in the attribute
                GL11.GL_FLOAT, // Data type of this attribute
                false, // Whether or not the attribute data is normalized
                4 * this.mFloatsPerVert, // Stride--Byte length of the entire vertex
                byteOffset // Byte offset for this attribute
            );
            // Offset by the number of bytes in that last attribute:
            byteOffset += 4L * this.mFloatsPerAttrib[indAttrib];
        }
        // Unbind the VBO for default state:
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        // Enable client state for data throughput:
        for (int indAttrib = 0; indAttrib < numAttribs; indAttrib++)
        {
            GL20.glEnableVertexAttribArray(indAttrib);
        }
        // Queue up the data in the GPU:
        GL11.glDrawArrays(
            this.mGeometryType, // Geometry type
            0, // Index of the first geometry to draw
            this.mNumVerts // Number of vertices to process
        );
        // Disable client state for data throughput:
        for (int indAttrib = 0; indAttrib < numAttribs; indAttrib++)
        {
            GL20.glDisableVertexAttribArray(indAttrib);
        }
    }
    
    public void clear()
    {
        this.mListVerts.clear();
        this.mNumVerts = 0;
        this.compileData();
        this.mCompiled = false;
    }
    
    public void onEnd()
    {
        GL15.glDeleteBuffers(this.mID);
        this.mNumVerts = 0;
    }
    
    
    public int numVerts()
    {
        return this.mNumVerts;
    }
    
    
    private int sumOver(int[] aArray)
    {
        int sum = 0;
        for (int elem : aArray)
        {
            sum += elem;
        }
        return sum;
    }
    
}
